//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Utils/Base/Meta.Common.h"
#include "Graphics/MetaData/Meta.Program.h"

#include "Utils/Shader/ShaderConfig/ShaderUtils.h"

#include "Shared/Resources/CryptKey.h"

namespace Engine
{
namespace Utils
{

	//
	// Shader Program Cofnig
	//

	class ShaderProgramCfg : public MetaData::ProgramCfg
	{
	// variables
	private:
		//int		_floatPrecision;
		//int		_intPrecision;


	// metadata
	public:
		static CMetaClass &		MetaShaderProgram ()	{ static CMetaClass metaClass;  return metaClass; }
		
		friend UX_CREATE_META_OBJECT( ShaderProgramCfg,  MetaShaderProgram() );

		void InitMetaData ()
		{
			ProgramCfg::InitMetaData();

			CMetaAddIteratorPtr	iter;

			if ( not MetaShaderProgram().IsInit() )
			{
				MetaShaderProgram().CreateExt( "ShaderProgram", *this );
				MetaShaderProgram().Inherit( ProgramCfg::GetMetaClass() );
				iter = MetaShaderProgram().GetAddIter( *this );

				//iter->AddField( this->_floatPrecision,	"float_precision" );
				//iter->AddField( this->_intPrecision,	"int_precision" );
			}
		}

		CMetaSetIterator * GetMetaIterator () const
		{
			return MetaShaderProgram().GetSetIter( *this );
		}


	// methods
	public:
		ShaderProgramCfg () //: _floatPrecision(0), _intPrecision(0)
		{}

		bool OnLoad (const string &fs, const string &vs, ShaderUtils *utils)
		{
			CHECK_ERR( _SomeChecks( fs, vs ) );

			fragmentShader	= fs;
			vertexShader	= vs;

			_AddAttribs( utils );
			_MinimizeSource();
			_CheckUniforms();

			//CHECK( _Optimize( utils ) );


			CHECK( _Validate( utils ) );
			return true;
		}


	private:

		void _MinimizeSource ()
		{
			_MinimizeShaderSource( fragmentShader, fragmentShader );
			_MinimizeShaderSource( vertexShader, vertexShader );
		}


		void _CheckUniforms ()
		{
			usize pos = 0;

			FOR( i, textures )
			{
				if ( not fragmentShader.Find( textures[i].uniform, pos, 0 ) and
					 not vertexShader.Find( textures[i].uniform, pos, 0 ) )
				{
					CHECK( false and "uniform not found!" );
				}
			}
		}


		static bool _CheckAttrib (const string &src)
		{
			usize	pos		= 0;
			usize	new_pos	= 0;

			array<uvec2>	intervals;

			// build intervals //

			// multiline
			while ( src.Find( "/*", new_pos, pos ) )
			{
				intervals.PushBack( uvec2( pos, new_pos ) );

				if ( not src.Find( "*/", pos, new_pos ) )
					break;

				pos += 2;
			}

			// one line
			pos = 0;

			while ( src.Find( "//", new_pos, pos ) )
			{
				usize	c10 = 0;
				usize	c13 = 0;

				src.Find( char(10), c10, new_pos );
				src.Find( char(13), c13, new_pos );

				pos = Min( c10, c13 );

				usize	i = -1;

				// find interval
				FOR( j, intervals )
				{
					if ( intervals[j].x >= pos and intervals[j].y <= pos )
					{
						i = j;
						break;
					}
				}

				if ( i != -1 )
				{
					uvec2 interv = intervals[i];

					intervals[i].y = pos;
					intervals.Insert( uvec2(new_pos, interv.y), i+1 );
				}
			}


			// check
			const uni_c_string	attr = "attribute";

			FOR( i, intervals )
			{
				if ( src.Find( attr, pos, intervals[i].x ) )
				{
					char	prev = pos != 0 ? src[ pos - 1 ] : 0;
					char	next = src[ pos + attr.Length() ];

					if ( pos <= intervals[i].y and
						(prev == /*space*/' ' or prev == /*tab*/'	' or prev == 10 or prev == 13 or prev == 0) and
						(next == /*space*/' ' or next == /*tab*/'	' or next == 10 or next == 13 or next == 0) )
					{
						return false;
					}
				}
			}
			return true;
		}
		

		static bool _SomeChecks (const string &fs, const string &vs)
		{
			bool	res = _CheckAttrib( fs ) and _CheckAttrib( vs );

			ASSERT_EXT( res, "All attributes must be defined in config file!" );
			return res;
		}


		void _AddAttribs (ShaderUtils *utils)
		{
			array< ShaderAttrib >	attr;

			FOR( i, attribs )
			{
				attr.PushBack( ShaderAttrib( attribs[i].index, attribs[i].name.cstr(), attribs[i].type ) );
			}

			string	attr_str;
			//string	precision;

			attr_str.Reserve( 255 );
			//precision.Reserve( 255 );

			int	len;
			len = attr_str.Capacity()-1;
			utils->GenAttribs( attr.ptr(), attr.Count(), attr_str.ptr(), len );
			attr_str.SetLength( len );
			
			//len = precision.Capacity()-1;
			//utils->GenPrecision( _floatPrecision, _intPrecision, precision.ptr(), len );
			//precision.SetLength( len );

			//fragmentShader.Insert( precision, 0 );
			vertexShader.Insert( attr_str, 0 );
			//vertexShader.Insert( precision, 0 );
		}


		bool _Optimize (string &fs, string &vs, ShaderUtils *utils)
		{
			return	utils->Optimize( fragmentShader, fragmentShader ) and
					utils->Optimize( vertexShader, vertexShader );
		}


		bool _Validate (ShaderUtils *utils) const
		{
			array< ShaderAttrib >	attr;

			FOR( i, attribs )
			{
				attr.PushBack( ShaderAttrib( attribs[i].index, attribs[i].name.cstr() ) );
			}

			array< const char *>	uniforms;

			FOR( i, textures )
			{
				uniforms.PushBack( textures[i].uniform.cstr() );
			}

			return utils->Validate( fragmentShader.cstr(), vertexShader.cstr(), attr.ptr(), attr.Count(),
									uniforms.Empty() ? null : uniforms.ptr(), uniforms.Count() );
		}


		static bool _IsWordChar (char c)
		{
			return c == '_' or (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or (c >= '0' and c <= '9');
		}


		static bool _IsSpecSymbol (char c)
		{
			return	c=='.' or c=='[' or c==']' or c=='(' or c==')' or c=='{' or c=='}' or c=='!' or c=='#' or
					c==';' or c==':' or c=='%' or c=='^' or c=='&' or c=='*' or c=='<' or c=='>' or c=='/' or
					c=='|' or c=='~' or c=='\\';
		}


		static void _MinimizeShaderSource (const string input, string &output)
		{
			bool	multiline_comment	= false;
			bool	oneline_comment		= false;
			bool	macro				= false;
			bool	macro_next_line		= false;

			output.ClearMem();
			output.Reserve( input.Length() );

			for (usize i = 0; i < input.Length(); ++i)
			{
				char c = input[i];
				char n = input[i+1];

			// process flags //
				if ( multiline_comment )
				{
					if ( c == '*' and n == '/' )
					{
						multiline_comment = false;
						++i;
					}
					continue;
				}

				if ( oneline_comment )
				{
					if ( c == 10 or c == 13 )
					{
						oneline_comment	= false;
					}
					continue;
				}

				if ( macro )
				{
					/*if ( macro_next_line )
					{
						macro_next_line = false;
					}*/

					if ( c == 10 or c == 13 )
					{
						output << char(10);
						macro	= false;
						++i;
						continue;
					}

					if ( c == '\\' and (n == 10 or n == 13) )
					{
						i += (n == 13) + 1;		// skip (13, 10) or (10)
						//macro_next_line = true;
						continue;
					}

					goto lbl_cut_some_chars;
				}


			// check flags //
				// multiline comment
				if ( c == '/' and n == '*' )
				{
					multiline_comment = true;
					++i;
					continue;
				}


				// one line comment
				if ( c == '/' and n == '/' )
				{
					oneline_comment	= true;
					++i;
					continue;
				}


				// macro
				if ( c == '#' )
				{
					macro	= true;
					goto lbl_cut_some_chars;
				}


			// cut some chars //
lbl_cut_some_chars:
				// tab to space
				if ( c == /*tab*/'	' )
				{
					c = /*space*/' ';
				}

				if ( n == /*tab*/'	' )
				{
					n = /*space*/' ';
				}

				char	lc = output.Empty() ? 0 : output.Back();

				if ( c == 10 or c == 13 )
				{
					c = 10;

					if ( n != '#' and not _IsWordChar( lc ) ) 
						continue;
				}

				if ( n == 13 )
				{
					n = 10;
				}

				if ( c == /*space*/' ' and (not _IsWordChar( lc ) or not _IsWordChar( n )) )
				{
					continue;
				}

				output << c;
			}
		}

	};


}	// Utils
}	// Engine